//
// Created by alexa on 5/13/2024.
//

#include "UnitsService.h"
#include "../../exceptions/EntityNotFound.h"

CombatUnit *UnitsService::getById(string id) {
    CombatUnit *unit = repository.findById(id);
    if (!unit) {
        throw EntityNotFound("Unit with this id wasn't found.");
    }
    return unit;
}

vector<CombatUnit *> UnitsService::getAll() {
    return repository.findAll();
}

void UnitsService::deleteOne(string id) {
    CombatUnit *unit = getById(id);
    Battalion *battalion = battalionsService->findByUnitId(id);
    if (battalion) {
        battalion->removeUnitId(id);
        battalionsService->saveBattalion(battalion);
    }
    repository.deleteOne(unit);
}

Launcher *UnitsService::createLauncher(string name, int railsCount, float shotsDelay) {
    Launcher *launcher = new Launcher(name, railsCount, shotsDelay);
    saveUnit(launcher);
    return launcher;
}

SelfPropelledLauncher *
UnitsService::createSelfPropelledLauncher(
        const string &name,
        int railsCount,
        float shotsDelay,
        float length,
        float width,
        float maxSpeed,
        float weight,
        float powerReserve,
        float preparingTime
) {
    SelfPropelledLauncher *selfPropelledLauncher = new SelfPropelledLauncher(
            name,
            railsCount,
            shotsDelay,
            length,
            width,
            maxSpeed,
            weight,
            powerReserve,
            preparingTime
    );
    saveUnit(selfPropelledLauncher);
    return selfPropelledLauncher;
}

void UnitsService::setCrew(string unitId, string crewId) {

    CombatUnit *unit = getById(unitId);
    if (crewId == "") {
        unit->setCrewId("");
        saveUnit(unit);
        return;
    }
    CombatUnit *prevUnit = findByCrewId(crewId);
    MaintenanceCrew *crew = crewsService->getById(crewId);
    if (prevUnit) {
        prevUnit->setCrewId("");
        saveUnit(prevUnit);
    }
    unit->setCrewId(crew->getId());
    saveUnit(unit);
}

bool UnitsService::isPropelledType(CombatUnit *unit) {
    SelfPropelledLauncher *launcher = dynamic_cast<SelfPropelledLauncher *>(unit);
    if (launcher) {
        return true;
    }
    return false;
}

CombatUnit *UnitsService::findByCrewId(string crewId) {
    vector<CombatUnit *> units = getAll();
    for (const auto &unit: units) {
        string curUnitId = unit->getCrewId();
        if (curUnitId == crewId) return unit;
    }
    return nullptr;
}

vector<CombatUnit *> UnitsService::getManyByIds(vector<string> ids) {
    vector<CombatUnit *> units;
    for (const auto &id: ids) {
        try {
            units.push_back(getById(id));
        } catch (EntityNotFound &pass) {}
    }
    return units;
}

void UnitsService::saveUnit(CombatUnit *unit) {
    repository.save(unit);
}

vector<Specialization> UnitsService::getMissingSpecializations(string unitId) {
    CombatUnit *unit = getById(unitId);
    if (unit->getCrewId().empty()) return unit->getRequiredSpecializations();
    vector<Specialization> missingSpecializations;
    return crewsService->getMissingSpecializationsInCrew(unit->getCrewId(),
                                                         unit->getRequiredSpecializations());
}

void UnitsService::setBattalionsService(BattalionsService *battalionsService) {
    this->battalionsService = battalionsService;
}

CombatUnit *UnitsService::findByMissileId(string missileId) {
    vector<CombatUnit *> units = getAll();
    for (const auto &unit: units) {
        string curMissileId = unit->getMissileId();
        if (curMissileId == missileId) return unit;
    }
    return nullptr;
}

UnitsService::UnitsService(CrewsService *crewsService, MissilesService *missilesService) : crewsService(crewsService),
                                                                                           missilesService(
                                                                                                   missilesService) {}

void UnitsService::setMissile(string unitId, string missileId) {
    CombatUnit *unit = getById(unitId);
    if (missileId == "") {
        unit->setMissileId("");
        saveUnit(unit);
        return;
    }
    Missile *missile = missilesService->getById(missileId);
    unit->setMissileId(missile->getId());
    saveUnit(unit);
}

float UnitsService::moveCombatUnit(string unitId, float newX, float newY) {
    CombatUnit *unit = getById(unitId);

    float curX = unit->getPosX();
    float curY = unit->getPosY();

    float distance = calculateDistance(curX, curY, newX, newY);

    float maxSpeed = 7;

    if (isPropelledType(unit)) {
        SelfPropelledLauncher *selfPropelledLauncher = dynamic_cast<SelfPropelledLauncher *>(unit);
        maxSpeed = selfPropelledLauncher->getMaxSpeed();
        float remainingPowerReserve = selfPropelledLauncher->getRemainingPowerReserve();
        if (distance > remainingPowerReserve) {
            // вычисляем на каких координатах будет боевая машина если проедет
            // remainingPowerReserve по заданой траектории к заданной точке
            newX = curX + (newX - curX) * (remainingPowerReserve / distance);
            newY = curY + (newY - curY) * (remainingPowerReserve / distance);
            distance = remainingPowerReserve;
            selfPropelledLauncher->setRemainingPowerReserve(0);
        } else {
            selfPropelledLauncher->setRemainingPowerReserve(remainingPowerReserve - distance);
        }
    }
    unit->setPosX(newX);
    unit->setPosY(newY);
    saveUnit(unit);
    return distance;
}

void UnitsService::refuelUnit(CombatUnit *unit) {
    if (!isPropelledType(unit)) return;
    SelfPropelledLauncher *launcher = dynamic_cast<SelfPropelledLauncher *>(unit);
    launcher->setRemainingPowerReserve(launcher->getPowerReserve());
    saveUnit(launcher);
}

void UnitsService::setAimForCombatUnit(string unitId, float aimX, float aimY) {
    CombatUnit *unit = getById(unitId);
    Launcher *launcher = dynamic_cast<Launcher *>(unit);
    launcher->setAimX(aimX);
    launcher->setAimY(aimY);
    saveUnit(launcher);
}

void UnitsService::reloadUnit(string unitId) {
    CombatUnit *unit = getById(unitId);
    Launcher *launcher = dynamic_cast<Launcher *>(unit);
    launcher->setMissilesCount(launcher->getRailsCount());
    saveUnit(launcher);
}

unordered_map<string, float> UnitsService::unitShoot(CombatUnit *unit) {
    unordered_map<string, float> res;
    Missile *missile = missilesService->getById(unit->getMissileId());
    float missileFlightRange = missile->getFlightRange();
    Launcher *launcher = dynamic_cast<Launcher *>(unit);
    float aimX = launcher->getAimX();
    float aimY = launcher->getAimY();

    float distance =
            powf(powf(aimX - launcher->getPosX(), 2) +
                 powf(aimY - launcher->getPosY(), 2), 0.5);
    if (distance > missileFlightRange) {
        aimX = launcher->getPosX() + (aimX - launcher->getPosX()) * (missileFlightRange / distance);
        aimY = launcher->getPosY() + (aimY - launcher->getPosY()) * (missileFlightRange / distance);
        distance = missileFlightRange;
    }
    res["aimX"] = aimX;
    res["aimY"] = aimY;
    res["distance"] = distance;
    unit->shoot();
    saveUnit(unit);
    return res;
}

